SDK regeneration - #57
Conversation
|
Important Review skippedToo many files! This PR contains 297 files, which is 147 over the limit of 150. To get a review, narrow the scope: Upgrade to Pro+ to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (297)
You can disable this status message by setting the Comment |
|
@coderabbitai Review this PR Please specifically point out interface breaking changes. These include but not limited to
|
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Branch and merge-base =="
git status --short
git branch --show-current
base="$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main)"
printf 'MERGE_BASE=%s\n' "$base"
printf 'HEAD=%s\n' "$(git rev-parse HEAD)"
echo
echo "== Changed files (name/status) =="
git diff --name-status "$base"...HEAD
echo
echo "== Changed-file summary =="
git diff --stat "$base"...HEAD
echo
echo "== Root documentation candidates =="
fd -HI '^reference\.md$|^README\.md$' . -d 2
echo
echo "== reference.md outline / public headings =="
if [ -f reference.md ]; then
rg -n '^(#{1,4})\s|^##\s+client\.v1|internal' reference.md | head -n 240
fi
echo
echo "== Generated SDK top-level candidates =="
fd -HI -t d 'truefoundry_sdk' . | head -n 40Length of output: 40741 🏁 Script executed: #!/bin/bash
set -euo pipefail
base="e73109c6cf800314789ce9a5a927ca38d104f6c0"
python3 - "$base" <<'PY'
import ast, subprocess, sys
base = sys.argv[1]
head = "HEAD"
root = "src/truefoundry_sdk/"
def files(rev):
text = subprocess.check_output(
["git", "ls-tree", "-r", "--name-only", rev, root], text=True
)
return [p for p in text.splitlines() if p.endswith(".py") and "/internal/" not in p]
def source(rev, path):
try:
return subprocess.check_output(["git", "show", f"{rev}:{path}"], text=True)
except subprocess.CalledProcessError:
return ""
def unparse(n):
return ast.unparse(n) if n is not None else None
def args(fn):
a = fn.args
out = []
positional = list(a.posonlyargs) + list(a.args)
defaults = [None] * (len(positional) - len(a.defaults)) + list(a.defaults)
for v, d in zip(positional, defaults):
if v.arg in ("self", "cls"):
continue
out.append((v.arg, unparse(v.annotation), d is not None))
if a.vararg:
out.append(("*" + a.vararg.arg, unparse(a.vararg.annotation), False))
for v, d in zip(a.kwonlyargs, a.kw_defaults):
out.append((v.arg, unparse(v.annotation), d is not None))
if a.kwarg:
out.append(("**" + a.kwarg.arg, unparse(a.kwarg.annotation), False))
return out
def parse(rev):
methods, classes, exports = {}, {}, {}
for path in files(rev):
tree = ast.parse(source(rev, path), filename=path)
module = path[len(root):-3].replace("/", ".")
for n in tree.body:
if isinstance(n, (ast.ClassDef,)):
bases = [unparse(x) for x in n.bases]
fields = {}
enum = {}
for item in n.body:
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
fields[item.target.id] = unparse(item.annotation)
elif isinstance(item, ast.Assign):
for target in item.targets:
if isinstance(target, ast.Name) and (
"Enum" in " ".join(bases) or "Literal" not in " ".join(bases)
):
# Collected only used where the class is confirmed Enum below.
enum[target.id] = unparse(item.value)
elif isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)) and not item.name.startswith("_"):
methods[f"{module}.{n.name}.{item.name}"] = args(item)
classes[f"{module}.{n.name}"] = {
"bases": bases, "fields": fields,
"enum": enum if any("Enum" in x for x in bases) else {}
}
elif isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and not n.name.startswith("_"):
methods[f"{module}.{n.name}"] = args(n)
if path.endswith("/__init__.py") or path == root + "__init__.py":
names = set()
for n in tree.body:
if isinstance(n, ast.ImportFrom):
names.update(a.asname or a.name for a in n.names if a.name != "*")
elif isinstance(n, ast.Assign):
if any(isinstance(t, ast.Name) and t.id == "__all__" for t in n.targets):
if isinstance(n.value, (ast.List, ast.Tuple)):
names.update(e.value for e in n.value.elts if isinstance(e, ast.Constant) and isinstance(e.value, str))
exports[module] = names
return methods, classes, exports
bm, bc, be = parse(base)
hm, hc, he = parse(head)
print("== Removed required/optional method parameters in public non-raw clients ==")
count = 0
for key in sorted(set(bm) & set(hm)):
# exclude raw client implementations; public standard client only
if ".raw_client." in key:
continue
old = {x[0]: x for x in bm[key]}
new = {x[0]: x for x in hm[key]}
removed = [old[k] for k in old.keys() - new.keys()]
if removed:
print(key, "REMOVED", removed)
count += 1
print("count:", count)
print("\n== Existing public methods with changed parameter annotation/defaultness ==")
count = 0
for key in sorted(set(bm) & set(hm)):
if ".raw_client." in key: continue
old, new = dict(bm[key]), dict(hm[key])
changes = [(k, old[k], new[k]) for k in old.keys() & new.keys() if old[k] != new[k]]
if changes:
print(key, changes)
count += 1
print("count:", count)
print("\n== Removed public client methods/classes ==")
for key in sorted(set(bm) - set(hm)):
if ".raw_client." not in key:
print("METHOD", key)
for key in sorted(set(bc) - set(hc)):
print("CLASS", key)
print("\n== Data-model fields removed or field annotations changed (non-Enum classes) ==")
count = 0
for key in sorted(set(bc) & set(hc)):
old, new = bc[key], hc[key]
if old["enum"] or new["enum"]:
continue
removed = sorted(set(old["fields"]) - set(new["fields"]))
changed = [(f, old["fields"][f], new["fields"][f])
for f in sorted(set(old["fields"]) & set(new["fields"]))
if old["fields"][f] != new["fields"][f]]
if removed or changed:
print(key, "removed_fields=", removed, "changed_fields=", changed)
count += 1
print("count:", count)
print("\n== Enum members removed or value changed ==")
count = 0
for key in sorted(set(bc) & set(hc)):
old, new = bc[key]["enum"], hc[key]["enum"]
if not old and not new: continue
removed = sorted(set(old) - set(new))
changed = [(e, old[e], new[e]) for e in sorted(set(old) & set(new)) if old[e] != new[e]]
if removed or changed:
print(key, "removed_members=", removed, "changed_values=", changed)
count += 1
print("count:", count)
print("\n== Removed imports/re-exports from package __init__ modules ==")
for key in sorted(set(be) & set(he)):
removed = sorted(be[key] - he[key])
if removed:
print(key, removed)
PY
echo
echo "== Exact public client and reference documentation diff (excluding internal) =="
git diff --unified=5 "$base"...HEAD -- \
reference.md \
'src/truefoundry_sdk/*/client.py' \
':(exclude)src/truefoundry_sdk/internal/**' \
| sed -n '1,2600p'
echo
echo "== Exact diffs of renamed/removed public type modules and package exports =="
git diff --find-renames --unified=5 "$base"...HEAD -- \
src/truefoundry_sdk/__init__.py \
src/truefoundry_sdk/types/__init__.py \
src/truefoundry_sdk/applications/types/__init__.py \
src/truefoundry_sdk/agents/types/__init__.py \
src/truefoundry_sdk/users/types/__init__.py \
'src/truefoundry_sdk/types/build_status.py' \
'src/truefoundry_sdk/types/deployment_build_status.py' \
'src/truefoundry_sdk/types/resource_type.py' \
'src/truefoundry_sdk/types/role_resource_type.py' \
'src/truefoundry_sdk/types/i_change_operation.py' \
'src/truefoundry_sdk/types/i_change_type.py' \
'src/truefoundry_sdk/types/gateway_logging_when.py' \
'src/truefoundry_sdk/types/agent_channel_agent.py' \
'src/truefoundry_sdk/types/gateway_logging_rule.py' \
'src/truefoundry_sdk/types/noma_security_api_key_auth.py' \
| sed -n '1,2600p'Length of output: 8101 🏁 Script executed: #!/bin/bash
set -euo pipefail
base="e73109c6cf800314789ce9a5a927ca38d104f6c0"
python3 - "$base" <<'PY'
import ast, subprocess, sys
base=sys.argv[1]; head="HEAD"; root="src/truefoundry_sdk/"
def git(rev,path):
try: return subprocess.check_output(["git","show",f"{rev}:{path}"],text=True)
except subprocess.CalledProcessError: return ""
def paths(rev):
x=subprocess.check_output(["git","ls-tree","-r","--name-only",rev,root],text=True).splitlines()
return [p for p in x if p.endswith(".py") and "/internal/" not in p]
def up(x): return ast.unparse(x) if x is not None else None
def params(node):
a=node.args; result=[]
pos=list(a.posonlyargs)+list(a.args)
defaults=[None]*(len(pos)-len(a.defaults))+list(a.defaults)
for x,d in zip(pos,defaults):
if x.arg not in ("self","cls"): result.append((x.arg,up(x.annotation),d is not None))
if a.vararg: result.append(("*"+a.vararg.arg,up(a.vararg.annotation),False))
for x,d in zip(a.kwonlyargs,a.kw_defaults): result.append((x.arg,up(x.annotation),d is not None))
if a.kwarg: result.append(("**"+a.kwarg.arg,up(a.kwarg.annotation),False))
return result
def load(rev):
meth={}; cls={}; exports={}
for path in paths(rev):
t=ast.parse(git(rev,path)); mod=path[len(root):-3].replace("/",".")
for n in t.body:
if isinstance(n,ast.ClassDef):
bases=[up(b) for b in n.bases]; fields={}; enum={}; methods={}
isenum=any("Enum" in b for b in bases)
for z in n.body:
if isinstance(z,ast.AnnAssign) and isinstance(z.target,ast.Name):
fields[z.target.id]=up(z.annotation)
elif isenum and isinstance(z,ast.Assign):
for target in z.targets:
if isinstance(target,ast.Name): enum[target.id]=up(z.value)
elif isinstance(z,(ast.FunctionDef,ast.AsyncFunctionDef)) and not z.name.startswith("_"):
methods[z.name]=params(z)
cls[f"{mod}.{n.name}"]={"bases":bases,"fields":fields,"enum":enum}
if path.endswith("/client.py") and not path.endswith("/raw_client.py"):
for name,p in methods.items(): meth[f"{mod}.{n.name}.{name}"]=p
elif path.endswith("/client.py") and not path.endswith("/raw_client.py") and isinstance(n,(ast.FunctionDef,ast.AsyncFunctionDef)) and not n.name.startswith("_"):
meth[f"{mod}.{n.name}"]=params(n)
if path.endswith("__init__.py"):
names=set()
for n in t.body:
if isinstance(n,ast.ImportFrom):
names |= {a.asname or a.name for a in n.names if a.name!="*"}
if isinstance(n,ast.Assign) and any(isinstance(q,ast.Name) and q.id=="__all__" for q in n.targets):
if isinstance(n.value,(ast.List,ast.Tuple)):
names |= {q.value for q in n.value.elts if isinstance(q,ast.Constant) and isinstance(q.value,str)}
exports[mod]=names
return meth,cls,exports
bm,bc,be=load(base); hm,hc,he=load(head)
print("== PUBLIC CLIENT METHODS: removed arguments ==")
n=0
for k in sorted(bm.keys() & hm.keys()):
o={v[0]:v for v in bm[k]}; h={v[0]:v for v in hm[k]}
r=[o[x] for x in o.keys()-h.keys()]
if r: print(k, r);n+=1
print("COUNT",n)
print("\n== PUBLIC CLIENT METHODS: changed compatible-looking parameter contract ==")
n=0
for k in sorted(bm.keys() & hm.keys()):
o={v[0]:v for v in bm[k]}; h={v[0]:v for v in hm[k]}
c=[(x,o[x],h[x]) for x in o.keys()&h.keys() if o[x][1:]!=h[x][1:]]
if c: print(k,c);n+=1
print("COUNT",n)
print("\n== PUBLIC CLIENT METHODS: removed ==")
for k in sorted(bm.keys()-hm.keys()): print(k)
print("\n== PUBLIC TYPES: fields removed or annotations changed ==")
n=0
for k in sorted(bc.keys()&hc.keys()):
o,h=bc[k],hc[k]
if o["enum"] or h["enum"]: continue
r=sorted(o["fields"].keys()-h["fields"].keys())
c=[(x,o["fields"][x],h["fields"][x]) for x in sorted(o["fields"].keys()&h["fields"].keys()) if o["fields"][x]!=h["fields"][x]]
if r or c: print(k,"REMOVED",r,"CHANGED",c);n+=1
print("COUNT",n)
print("\n== PUBLIC ENUMS: members removed or values changed ==")
n=0
for k in sorted(bc.keys()&hc.keys()):
o,h=bc[k]["enum"],hc[k]["enum"]
if not o and not h: continue
r=sorted(o.keys()-h.keys())
c=[(x,o[x],h[x]) for x in sorted(o.keys()&h.keys()) if o[x]!=h[x]]
if r or c: print(k,"REMOVED",r,"CHANGED",c);n+=1
print("COUNT",n)
print("\n== PUBLIC MODULE CLASSES REMOVED ==")
for k in sorted(bc.keys()-hc.keys()): print(k)
print("\n== REMOVED RE-EXPORTS ==")
for k in sorted(be.keys()&he.keys()):
r=sorted(be[k]-he[k])
if r: print(k,r)
PY
echo
echo "== Client/signature and renamed public-type diffs (non-internal) =="
git diff --find-renames --unified=4 "$base"...HEAD -- \
'src/truefoundry_sdk/*/client.py' \
':(exclude)src/truefoundry_sdk/internal/**' \
src/truefoundry_sdk/__init__.py \
src/truefoundry_sdk/types/__init__.py \
src/truefoundry_sdk/applications/__init__.py \
src/truefoundry_sdk/applications/types/__init__.py \
src/truefoundry_sdk/agents/__init__.py \
src/truefoundry_sdk/agents/types/__init__.py \
src/truefoundry_sdk/users/__init__.py \
src/truefoundry_sdk/users/types/__init__.py \
| rg -n -C3 '^(diff|@@)|^[-+].*(def |import |[A-Z][A-Za-z0-9_]+\b|typing\.)' | sed -n '1,3000p'
echo
echo "== Diffs for two removed public union variants =="
git diff --unified=8 "$base"...HEAD -- \
src/truefoundry_sdk/types/role_binding_subject_type.py \
src/truefoundry_sdk/types/subject_type.py \
src/truefoundry_sdk/types/role_binding_subject.py \
src/truefoundry_sdk/types/subject.py \
| sed -n '1,1000p'Length of output: 149020 🏁 Script executed: #!/bin/bash
set -euo pipefail
base="e73109c6cf800314789ce9a5a927ca38d104f6c0"
echo "== Current line locations for confirmed breaking changes =="
for f in \
src/truefoundry_sdk/__init__.py \
src/truefoundry_sdk/types/budget_v2alert.py \
src/truefoundry_sdk/types/gateway_logging_config.py \
src/truefoundry_sdk/types/true_foundry_agent_user_message.py \
src/truefoundry_sdk/types/identity_provider_backed_identity.py \
src/truefoundry_sdk/types/role_binding_subject_type.py \
src/truefoundry_sdk/types/subject_type.py \
src/truefoundry_sdk/types/true_foundry_agent_mcp_server.py \
src/truefoundry_sdk/types/true_foundry_agent_skill.py \
src/truefoundry_sdk/types/mcp_server.py \
src/truefoundry_sdk/types/true_foundry_agent_mcp_server_inline.py \
src/truefoundry_sdk/types/true_foundry_agent_true_foundry_skill.py \
src/truefoundry_sdk/agents/raw_client.py \
src/truefoundry_sdk/artifact_versions/raw_client.py
do
echo "--- $f"
if [ -f "$f" ]; then
cat -n "$f" | sed -n '1,260p'
else
echo "(absent at HEAD)"
fi
done
echo
echo "== Exact diffs for all detected model field/type breaks =="
git diff --unified=6 "$base"...HEAD -- \
src/truefoundry_sdk/types/agent_identity_manifest.py \
src/truefoundry_sdk/types/budget_v2alert.py \
src/truefoundry_sdk/types/deployment_build.py \
src/truefoundry_sdk/types/gateway_logging_config.py \
src/truefoundry_sdk/types/gcp_gke_integration.py \
src/truefoundry_sdk/types/i_change.py \
src/truefoundry_sdk/types/identity_provider_backed_identity.py \
src/truefoundry_sdk/types/mcp_server_o_auth2.py \
src/truefoundry_sdk/types/team_budget_config.py \
src/truefoundry_sdk/types/tenant_budget_config.py \
src/truefoundry_sdk/types/true_foundry_agent_user_message.py \
src/truefoundry_sdk/types/true_foundry_apply_response.py \
| sed -n '1,2400p'
echo
echo "== Old removed agent union definitions (base) =="
for f in \
src/truefoundry_sdk/types/true_foundry_agent_mcp_server.py \
src/truefoundry_sdk/types/true_foundry_agent_skill.py \
src/truefoundry_sdk/types/gateway_logging_rule.py \
src/truefoundry_sdk/types/gateway_logging_when.py
do
echo "--- $f @ base"
git show "$base:$f" | cat -n | sed -n '1,240p'
doneLength of output: 74234
No public However, this PR contains several interface-breaking changes:
Given the number of removed symbols and incompatible model changes, this should be treated as a major-version release unless compatibility aliases/adapters are restored for the old public names and model shapes. ✅ Action performedReview finished.
|
Breaking Changes Report:
|
| Branch | chiragjn-nullable-optional |
| Base | main |
| Commits | 2619285, 681ee5f, 9e3ad50, edc6e38 (SDK regeneration) |
| Diff size | ~293 files, +9414 / −1995 |
| Last updated for | edc6e38 |
| Scope | Public SDK only (client.*, package exports, types/). client.internal.* ignored. |
| Note | This SDK has no client.v1 prefix; public methods are client.<resource>.<method>. |
Verdict: No public methods were removed and no method arguments were deleted. Breaking impact is concentrated in renamed/removed exported types, enum member removals, model shape changes (especially gateway logging and agent MCP/skill types), and narrowed list-filter parameter types (bare scalars no longer accepted).
Update from edc6e38 (vs previous report)
| Change | vs main impact |
|---|---|
Budget *Dto type renames (BudgetUsageResponseDto → BudgetUsageResponse, etc.) |
None for main upgraders — client.gateway_budgets is new on this branch; only breaks code written against intermediate branch commits |
CreateOrUpdateBudgetDtoManifest → CreateOrUpdateBudgetRequestManifest |
Same — new API only |
PySparkTaskConfig.image: TaskPySparkBuild → PySparkTaskConfigImage (Union[TaskPySparkBuild, TaskSparkImage]) |
Soft — existing TaskPySparkBuild values remain valid; annotations expecting exactly TaskPySparkBuild need updating |
CiscoAiDefenseGuardrailConfig added to guardrail union |
Additive (non-breaking) |
Severity legend
| Level | Meaning |
|---|---|
| Hard | Import fails, attribute missing, constructor/validation fails, or enum member gone |
| Soft | Type rename / annotation narrowing — often still works at runtime with string/dict inputs, but breaks typed code and some call patterns |
1. Removed public exports / types (Hard)
These names are no longer exported from truefoundry_sdk (and their modules were deleted or replaced).
| Removed symbol | Replacement / notes |
|---|---|
ResourceType |
UpdateUserRolesRequestResourceType (used by client.users.update_roles). Members are a superset of the old enum (added AGENT_CHANNEL, GATEWAY_APPROVAL_REQUEST, GATEWAY_POLICY). |
BuildStatus |
DeploymentBuildStatus (same members: STARTED, SUCCEEDED, FAILED) |
IChangeOperation |
IChangeType (same members: REMOVE, ADD, UPDATE) |
GatewayLoggingRule |
Removed with logging-config redesign (see §3) |
GatewayLoggingWhen |
Replaced conceptually by LoggingWhen (different shape) |
AgentsListRequestType |
ListAgentsRequestType |
ApplicationsListRequestDeviceTypeFilter |
ListApplicationsRequestDeviceTypeFilter |
ApplicationsListRequestLifecycleStage |
ListApplicationsRequestLifecycleStage |
ApplicationsCancelDeploymentResponse |
CancelDeploymentApplicationsResponse |
Deleted type modules:
src/truefoundry_sdk/types/build_status.pysrc/truefoundry_sdk/types/gateway_logging_rule.pysrc/truefoundry_sdk/types/gateway_logging_when.pysrc/truefoundry_sdk/types/i_change_operation.pysrc/truefoundry_sdk/types/resource_type.py
2. Enum member removals (Hard)
| Enum | Removed member | Value |
|---|---|---|
SubjectType |
EXTERNAL_IDENTITY |
"external-identity" |
RoleBindingSubjectType |
EXTERNAL_IDENTITY |
"external-identity" |
Impact:
SubjectType.EXTERNAL_IDENTITY/RoleBindingSubjectType.EXTERNAL_IDENTITYattribute access fails..visit(...)callbacks that previously requiredexternal_identity=will fail (parameter removed fromvisitsignature).Subject.subject_typenow types asSubjectSubjectType(new export). Relative to oldSubjectType, that type also lacksEXTERNAL_IDENTITY.
No other enum member removals were found across the public type tree. (VertexRegion only adds members.)
3. Model / type shape breaking changes (Hard)
3.1 GatewayLoggingConfig — redesigned
| Before | After | |
|---|---|---|
| Required payload | rules: List[GatewayLoggingRule] |
name: str, log: bool |
| Conditions | Per-rule GatewayLoggingWhen (subjects: Optional[List[str]]) |
Top-level when: Optional[LoggingWhen] (subjects/models as InNotIn, plus metadata) |
| Removed | rules, GatewayLoggingRule, GatewayLoggingWhen |
— |
| Added | — | name, log, when, redact_with |
Any code constructing or reading GatewayLoggingConfig.rules breaks.
3.2 BudgetV2Alert.notify_breaching_user removed
Field notify_breaching_user: Optional[bool] removed from BudgetV2Alert. Keyword/getattr usage breaks; leftover JSON may still parse if extra="allow", but the attribute is gone from the model.
3.3 TrueFoundryAgentUserMessage discriminator change
| Before | After |
|---|---|
role: Literal["user"] (default "user") |
removed |
| — | type: Literal["user.message"] (default "user.message") |
Code that sets/reads .role breaks. .type is the new discriminator.
3.4 TrueFoundryAgentMcpServer — class → discriminated union
Before: single model with name, preload, tool lists (no type / url).
After:
TrueFoundryAgentMcpServer = Union[
TrueFoundryAgentMcpServerRegistry, # type="truefoundry-mcp-registry", name=...
TrueFoundryAgentMcpServerInline, # type="inline", name=..., url=...
]Old instances without a type discriminator (and without url for inline) are no longer valid as the previous flat class.
3.5 TrueFoundryAgentSkill — class → discriminated union
Before: model with fqn, preload.
After:
TrueFoundryAgentSkill = Union[
TrueFoundryAgentTrueFoundrySkill, # type="truefoundry-skills-registry", fqn=..., preload=...
TrueFoundryAgentGitSourceSkill, # type="git", url=..., name=..., ref=..., path=?
]Constructing the old flat {fqn, preload} object as TrueFoundryAgentSkill(...) no longer works; use TrueFoundryAgentTrueFoundrySkill (or include type).
3.6 IdentityProviderBackedIdentity.type literal changed
| Before | After |
|---|---|
"idp-backed" |
"identity-provider-backed" |
Breaks discriminator checks / serialized payloads that hard-code the old literal.
3.7 TrueFoundryApplyResponse.data type changed
| Before | After |
|---|---|
Optional[Dict[str, Any]] |
Optional[TrueFoundryApplyResponseData] (union of entity models) |
Dict-style access patterns (response.data["id"], etc.) may break depending on runtime object type.
3.8 Field type renames (import / annotation breaks)
| Type.field | Before | After |
|---|---|---|
DeploymentBuild.status |
Optional[BuildStatus] |
Optional[DeploymentBuildStatus] |
IChange.type |
IChangeOperation |
IChangeType |
GcpGkeIntegration.location |
GcpRegion |
GcpGkeIntegrationLocation (superset; all old GcpRegion values remain) |
Subject.subject_type |
SubjectType |
SubjectSubjectType |
PySparkTaskConfig.image |
TaskPySparkBuild |
PySparkTaskConfigImage = Union[TaskPySparkBuild, TaskSparkImage] (widening; TaskPySparkBuild still accepted) |
4. Client method breaking / soft-breaking changes
4.1 No removed methods; no removed arguments
Public method inventory: 142 → 150. Added (non-breaking):
client.agents.get_tokenclient.gateway_budgets.*(create_or_update,delete,get,get_leaderboard,get_my_usage,list,simulate)
Current public type names for the new gateway budgets API (after edc6e38):
| Method | Parameter / return | Type |
|---|---|---|
client.gateway_budgets.create_or_update |
manifest |
CreateOrUpdateBudgetRequestManifest |
client.gateway_budgets.get_leaderboard / get_my_usage / simulate |
return | BudgetUsageResponse |
4.2 Scalar→sequence narrowing (Soft → Hard for some call sites)
These parameters no longer accept a bare scalar; pass a sequence/list.
Optional[Union[T, Sequence[T]]] → Optional[Sequence[T]]
| Method | Parameter(s) |
|---|---|
client.agents.list |
attributes |
client.artifact_versions.list |
run_ids, run_steps |
client.clusters.get_addons |
attributes |
client.clusters.list |
attributes |
client.events.get |
pod_names |
client.jobs.list_runs |
triggered_by, version_numbers |
client.logs.get |
pod_names |
client.ml_repos.list |
attributes |
client.model_versions.list |
run_ids, run_steps |
client.secret_groups.list |
attributes |
client.teams.list |
attributes |
client.virtual_accounts.list |
owned_by_teams |
client.workspaces.list |
attributes |
Example break: client.agents.list(attributes="id") → use attributes=["id"].
4.3 Request/response type renames on methods (Hard for typed imports)
| Method | Parameter / return | Before | After |
|---|---|---|---|
client.agents.list |
type |
AgentsListRequestType |
ListAgentsRequestType |
client.applications.list |
device_type_filter |
ApplicationsListRequestDeviceTypeFilter |
ListApplicationsRequestDeviceTypeFilter |
client.applications.list |
lifecycle_stage |
ApplicationsListRequestLifecycleStage |
ListApplicationsRequestLifecycleStage |
client.users.update_roles |
resource_type |
ResourceType |
UpdateUserRolesRequestResourceType |
client.applications.cancel_deployment |
return | ApplicationsCancelDeploymentResponse |
CancelDeploymentApplicationsResponse |
5. Non-breaking / additive type changes (for context)
Not breaking by themselves, but useful for migration awareness:
- Several fields became optional:
AgentIdentityManifest.owned_by,McpServerOAuth2.jwt_source,McpServerOAuth2.token_url,TeamBudgetConfig.when,TenantBudgetConfig.when. - Union widenings:
AgentChannelManifestadded to apply/delete manifest unions;NomaSecurityGuardrailConfigandCiscoAiDefenseGuardrailConfigadded to guardrail integrations;ComplexityBasedLoadBalancingadded to header routing;TaskSparkImageadded as alternatePySparkTaskConfig.image;GatewayLoggingConfigreordered insideGatewayConfigunion (still present). - Many new exports (gateway budgets, agent channels, MCP variants, etc.).
5.1 Intermediate-branch-only renames (edc6e38)
These symbols never existed on main. They only break consumers of earlier commits on this branch:
| Previous branch name | Current name |
|---|---|
BudgetEntityUsageDto |
BudgetEntityUsage |
BudgetUsageEntryDto |
BudgetUsageEntry |
BudgetUsageEntryDtoMode |
BudgetUsageEntryMode |
BudgetUsageEntryDtoType |
BudgetUsageEntryType |
BudgetUsageResponseDto |
BudgetUsageResponse |
PeriodUsageDto |
PeriodUsage |
CreateOrUpdateBudgetDtoManifest |
CreateOrUpdateBudgetRequestManifest |
6. Migration checklist (highest impact first)
- Replace imports for renamed/removed types in §1 (
ResourceType,BuildStatus,IChangeOperation, list-request enums, cancel-deployment response). - Stop using
SubjectType.EXTERNAL_IDENTITY/RoleBindingSubjectType.EXTERNAL_IDENTITY; update any.visit(external_identity=...)call sites. - Rewrite
GatewayLoggingConfigconstruction/consumption for the newname/log/when/redact_withmodel; droprules/GatewayLoggingRule. - Update agent manifests:
- MCP servers →
TrueFoundryAgentMcpServerRegistryorTrueFoundryAgentMcpServerInlinewithtype. - Skills →
TrueFoundryAgentTrueFoundrySkillorTrueFoundryAgentGitSourceSkillwithtype. - User messages →
type="user.message"instead ofrole="user".
- MCP servers →
- Fix list filters that passed a bare
str/float— wrap in a list/sequence (§4.2). - Update
IdentityProviderBackedIdentitydiscriminator to"identity-provider-backed". - Remove uses of
BudgetV2Alert.notify_breaching_user. - Revisit
client.applyresponse handling if code treateddataasdict. - Optional: if typing
PySparkTaskConfig.image, usePySparkTaskConfigImage(still passTaskPySparkBuildat runtime).
7. Analysis method / coverage
| Check | How |
|---|---|
| Method add/remove/params | Parsed reference.md (main vs HEAD); verified with AST on **/client.py (excl. internal) |
| Public export add/remove | Diffed src/truefoundry_sdk/__init__.py exports |
| Type field / enum / alias diffs | AST compare of shared type modules under types/ and resource types/ |
| Removed type modules | git ls-tree diff of src/truefoundry_sdk/types/ |
| Latest commit refresh | git diff HEAD~1 / export diff for edc6e38 |
Progress checklist
- Method inventory from
reference.md - Method parameter / return-type diffs (docs + client AST)
- Type field removals / type changes
- Enum member removals
- Removed public exports / types
- Final summary
- Refreshed for
edc6e38
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e2836b5. Configure here.
| <dd> | ||
|
|
||
| **lifecycle_stage:** `typing.Optional[ApplicationsListRequestLifecycleStage]` — Application lifecycle stage to filter by | ||
| **lifecycle_stage:** `typing.Optional[ListApplicationsRequestLifecycleStage]` — Application lifecycle stage to filter by |
There was a problem hiding this comment.
Public request type names renamed
High Severity
Public types were renamed without aliases, which breaks existing imports. ApplicationsListRequestDeviceTypeFilter / ApplicationsListRequestLifecycleStage became ListApplicationsRequest*, ApplicationsCancelDeploymentResponse became CancelDeploymentApplicationsResponse, and AgentsListRequestType became ListAgentsRequestType. This violates the PR request to flag interface-breaking type renames/removals in the public SDK.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit e2836b5. Configure here.
| <dd> | ||
|
|
||
| **owned_by_teams:** `typing.Optional[typing.Union[str, typing.Sequence[str]]]` — Comma-separated team names. Return virtual accounts owned by these teams. | ||
| **owned_by_teams:** `typing.Optional[typing.List[str]]` — Comma-separated team names. Return virtual accounts owned by these teams. |
There was a problem hiding this comment.
Array params no longer accept strings
Medium Severity
Several public list filters narrowed from Union[str, Sequence[...]] to List[...] only, including owned_by_teams, triggered_by, version_numbers, pod_names, run_ids, run_steps, and multiple attributes parameters. Call sites that passed a bare string will no longer type-check. This violates the PR request to flag public API argument/type breaking changes.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit e2836b5. Configure here.
| <dd> | ||
|
|
||
| **resource_type:** `typing.Optional[ResourceType]` — Resource type scope for the role assignment. | ||
| **resource_type:** `typing.Optional[UpdateUserRolesRequestResourceType]` — Resource type scope for the role assignment. |
There was a problem hiding this comment.
Roles resource type renamed
Medium Severity
client.users.update_roles changed resource_type from ResourceType to UpdateUserRolesRequestResourceType. Imports and annotations using the old public type name will break. This violates the PR request to flag breaking type renames in the public SDK.
Reviewed by Cursor Bugbot for commit e2836b5. Configure here.


Note
Medium Risk
Large auto-generated diff with new clients and renamed types that can break typed imports and call sites; runtime behavior follows the backing API spec.
Overview
Regenerates the TrueFoundry Python SDK from the updated API spec using Fern CLI 5.82.0 and fern-python-sdk 5.24.0 (from 5.59 / 5.15), with a new
originGitCommitin.fern/metadata.json.The generated surface adds
client.gateway_budgets(list, create/update manifests, get/delete, usage, simulate, leaderboard) andclient.agents.get_tokenfor on-demand agent identity tokens.users.deletegains optionalforce_deleteto strip collaborations and team memberships before removal.Across clients, list/filter parameters are typed as
typing.List[...]instead of comma-separatedUnion[str, Sequence[...]], and several request enum types are renamed (e.g.ApplicationsListRequest*→ListApplicationsRequest*, internal AI gateway/metrics types). Response type names align with the new generator (e.g. cancel deployment, internal workflows).README.mddocuments per-request timeout asrequest_options["timeout"]instead oftimeout_in_seconds.poetry.lockpicks up patch bumps (e.g. aiohttp, httpx-aiohttp);pyproject.tomlrelaxes thehttpx-aiohttpversion constraint to^0.1.8.Reviewed by Cursor Bugbot for commit e2836b5. Bugbot is set up for automated code reviews on this repo. Configure here.